package com.worldcup.conf.filter;

import org.apache.commons.io.output.TeeOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author duwan.zq
 * @date 2018/06/02
 */
public class RequestLoggingFilter extends OncePerRequestFilter {

    private final static Logger logger = LoggerFactory.getLogger("RequestLoggingFilter");

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        beforeRequest(httpServletRequest);
        httpServletResponse = new LoggingResponseWrapper(httpServletResponse);
        long startTime = System.currentTimeMillis();
        try{
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }finally {
            afterRequest((LoggingResponseWrapper)httpServletResponse, System.currentTimeMillis() - startTime);
        }
    }

    private void beforeRequest(HttpServletRequest request) throws IOException {
        String wholeURI = request.getRequestURI() + request.getQueryString();
        logger.info("The URI of request is: " + wholeURI);
        if(request.getMethod().equalsIgnoreCase(RequestMethod.GET.name())){
           return;
        }
        //todo 不能直接读，需要复制
        //byte[] buffer = IOUtils.toByteArray(request.getInputStream());
        //logger.info("The contents of request body is: \n" + new String(buffer, "UTF-8") + "\n");
    }

    private void afterRequest(LoggingResponseWrapper response,long useTime) throws IOException {
        logger.info("The contents use time " + useTime + ", response body is: \n"  + new String(response.toByteArray(), "UTF-8") + "\n");
    }


    protected class LoggingResponseWrapper extends HttpServletResponseWrapper {

        private final ByteArrayOutputStream bos = new ByteArrayOutputStream();

        private PrintWriter writer = new PrintWriter(bos);

        public LoggingResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public ServletResponse getResponse() {
            return this;
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            return new ServletOutputStream() {

                private TeeOutputStream tee = new TeeOutputStream(LoggingResponseWrapper.super.getOutputStream(), bos);

                @Override
                public void write(int b) throws IOException {
                    tee.write(b);
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setWriteListener(WriteListener writeListener) {

                }
            };
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            return new LoggingPrintWriter(super.getWriter(), writer);
        }

        public byte[] toByteArray(){
            return bos.toByteArray();
        }
    }



    protected class LoggingPrintWriter extends PrintWriter{

        PrintWriter branch;

        public LoggingPrintWriter(PrintWriter main, PrintWriter branch) {
            super(main, true);
            this.branch = branch;
        }

        @Override
        public void write(char buf[], int off, int len) {
            super.write(buf, off, len);
            super.flush();
            branch.write(buf, off, len);
            branch.flush();
        }
        @Override

        public void write(String s, int off, int len) {
            super.write(s, off, len);
            super.flush();
            branch.write(s, off, len);
            branch.flush();
        }
        @Override

        public void write(int c) {
            super.write(c);
            super.flush();
            branch.write(c);
            branch.flush();
        }
        @Override

        public void flush() {
            super.flush();
            branch.flush();
        }
    }


}
